﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using Platform.Infrastructure;
using Platform.Linq;
using Platform.ObjectModel;
using Platform.Storage.Entity;

namespace Platform.Storage.Impl
{
	internal class EntityQueryTranslator : ExpressionVisitor, IQueryTranslator
	{
		

		public EntityQueryTranslator()
			: this( null )
		{ }

		public EntityQueryTranslator( SymbolTable symbols )
		{
			this.Symbols = symbols ?? new SymbolTable();
		}

		public SymbolTable Symbols
		{ get; private set; }

		public IEntityMapper Mapper
		{
			get;
			set;
		}

		//public IExpressionExpander Expander
		//{ get; set; }

		//public IQueryable<FieldValue> Fields
		//{ get; set; }

		//public IQueryable<DataObjectRelationship> Relationship
		//{ get; set; }


		public Expression MapInstance( Type instanceType )
		{
			return Expression.Constant( this.Fields );
		}

		private bool ShouldExpand( Expression node )
		{
			return node.Find<MemberExpression>( m => this.Mapper.MappingExistsFrom( m.Member.DeclaringType ) ) != null;
		}

		private Expression Expand( Expression node )
		{
			
		}

		
		protected override Expression VisitBinary( BinaryExpression node )
		{
			if ( this.ShouldExpand( node.Left )
				|| this.ShouldExpand( node.Right ) )
			{
				return Expression
					.MakeBinary(
						ExpressionType.And,
						this.Expand( node.Left ),
						this.Expand( node.Right ) );
			}
			else
				return base.VisitBinary( node );
		}

		protected override Expression VisitUnary( UnaryExpression node )
		{
			switch ( node.NodeType )
			{
				// Retira essas expressões
				case ExpressionType.Quote:
					return base.Visit( node.Operand );
				default:
					return base.VisitUnary( node );
			}
		}

		protected override Expression VisitLambda<T>( Expression<T> node )
		{
			return Expression.Lambda( base.Visit( node.Body ), this.VisitRange( node.Parameters ).ToArray() );
		}

		protected override Expression VisitConstant( ConstantExpression node )
		{
			if ( node.Type.IsQueryable()
				&& node.Type.IsGenericType )
			{
				if ( !node.Type.GetGenericArguments()[ 0 ].IsValueType )
					return Expression.Constant( this.Mapper.FieldsFor( node.Type ) );
			}
			return node;
		}

		protected override Expression VisitParameter( ParameterExpression node )
		{
			Symbol s;

			// Verifica se já foi declarado 
			if ( this.Symbols.TryGetSymbol( node, out s ) )
				return s.Reference;

			// Existe mapeamento?
			if ( this.Mapper.MappingExistsFrom( node.Type ) )
			{
				return this.Symbols
					.DeclEntity( node,
						Expression.Parameter( this.Mapper.TypeFrom( node.Type ), node.Name )
					).Reference;
			}
			else
			{
				return base.VisitParameter( node );
			}
		}

		

		protected override Expression VisitMethodCall( MethodCallExpression node )
		{
			if ( node.Method.DeclaringType == typeof( Queryable ) )
			{
				// Necessita expandir a cláusula e 
				if ( node.Method.Name == "Where" )
				{
					return this.Map( node );
			
					//return Expression
					//	.Call( node.Method.MakeGenericMethod( arguments[ 0 ].Type ), arguments );
				}
			}
			return base.VisitMethodCall( node );
		}

		private IEnumerable<TItem> VisitRange<TItem>( IEnumerable<TItem> list )
			where TItem : Expression
		{
			foreach ( var e in list )
				yield return ( TItem )base.Visit( e );
		}

		private MethodCallExpression Map( MethodCallExpression node )
		{
			var methodInfo = node.Method.DeclaringType.GetMethod( node.Method.Name );
			return Expression.Call( methodInfo, this.VisitRange( node.Arguments ) );
		}

		

		#region IQueryTranslator Members

		public Expression Translate( Expression source )
		{

			return base.Visit( source );
		}

		#endregion




		internal sealed class ExpressionExpander : ExpressionVisitor
		{
			private EntityQueryTranslator translator;
			private bool stops = false;

			public ExpressionExpander( EntityQueryTranslator entity )
			{
				this.translator = entity;
			}

			public Expression Expand( Expression node )
			{
				this.stops = false;
				return this.Visit( node );
			}


			public override Expression Visit( Expression node )
			{
				return base.Visit( node );
			}

			protected override Expression VisitBinary( BinaryExpression node )
			{
				// Expanda cada lado da expressão binária
				var left = base.Visit( node.Left );
				var right = base.Visit( node.Right );

				return Expression.And( left, right );
			}

			protected override Expression VisitConstant( ConstantExpression node )
			{
				return null;
				//return base.VisitConstant( node );
			}

			protected override Expression VisitMember( MemberExpression node )
			{
				string fieldName = node.GetPropertyName();
				var parameter = node.Find<ParameterExpression>();
				Symbol symbol;
				
				// Se é um símbolo que foi trocado, então faz toda a alteração
				if ( this.translator.Symbols.TryGetSymbol( parameter, out symbol ) )
					return this.MakeFieldEquals( ( ParameterExpression )symbol.Reference, fieldName );
				else
					return node;
			}

			private Expression MakeFieldEquals( ParameterExpression fieldExpression, string propertyName )
			{
				return Expression.Equal(
					Expression.MakeMemberAccess( fieldExpression, typeof( FieldValue ).GetProperty( "LoweredFieldName" ) ),
					Expression.Constant( this.translator.Mapper.GetFieldNamespace( fieldExpression.Type, propertyName ) ) );
			}

		}

	}

	public interface IEntityMapper
	{
		bool MappingExistsFrom( Type source );

		//Type TypeFrom( Type source );

		object FieldsFor( Type source );

		string GetFieldNamespace( Type source, string propertyName );

		//IQueryable<FieldValue> FieldsFor( Type entity );

		//IQueryable<DataObjectRelationship> RelationsFor( Type entity );

	//	Expression Expand( Expression node );

	}




	public class DictionaryMapper : IEntityMapper
	{
		private List<Tuple<Type, DataObjectName>> schema = new List<Tuple<Type, DataObjectName>>();

		public IQueryable<FieldValue> Fields
		{ get; set; }

		#region IEntityMapper Members

		public bool MappingExistsFrom( Type source )
		{
			return this.schema.Any( tuple => tuple.Item1.Equals( source ) );
		}

		public object FieldsFor( Type source )
		{
			if ( this.MappingExistsFrom( source ) )
				return this.Fields;
			else
				return null;
		}

		public string GetFieldNamespace( Type source, string propertyName )
		{
			var tuple = this.schema.FirstOrDefault( t => t.Item1.Equals( source ) );

			if ( tuple != null )
				return tuple.Item2.FieldName( propertyName );
			else
				return propertyName;
		}

		#endregion
	}


	public class QueryExpander : IExpressionExpander
	{

		private Dictionary<Type, object> instanceMap = new Dictionary<Type, object>();


		public void Map<TSource>( object instance )
		{
			this.Map( typeof( TSource ), instance );
		}

		public void Map( Type source, object instance )
		{
			Arguments.IsNotNull( "instance", instance );
			this.instanceMap[ source ] = instance;
		}

		#region IEntityMapper Members

		public bool MappingExistsFrom( Type source )
		{
			return this.instanceMap.ContainsKey( source );
		}

		public Type TypeFrom( Type source )
		{
			object value = this.InstanceFrom( source );
			if ( value == null )
				throw new InvalidOperationException( "O tipo não existe." );
			return value.GetType();
		}

		public object InstanceFrom( Type source )
		{
			object value;
			if ( this.instanceMap.TryGetValue( source, out value ) )
				return value;
			else
				return null;
		}

		public IQueryable<FieldValue> FieldsFor( Type entity )
		{
			object value;
			if ( this.instanceMap.TryGetValue( entity, out value ) )
				return ( IQueryable<FieldValue> )value;
			else
				return Enumerable.Empty<FieldValue>().AsQueryable();
		}

		public IQueryable<DataObjectRelationship> RelationsFor( Type entity )
		{
			object value;
			if ( this.instanceMap.TryGetValue( entity, out value ) )
				return ( IQueryable<DataObjectRelationship> )value;
			else
				return Enumerable.Empty<DataObjectRelationship>().AsQueryable();
		}

		#endregion


		/// <summary>
		/// 
		/// </summary>
		/// <param name="node"></param>
		/// <returns></returns>
		public virtual bool ShouldExpand( Expression node )
		{
			return node.NodeType == ExpressionType.MemberAccess
				&& this.instanceMap.ContainsKey( ( ( MemberExpression )node ).Member.DeclaringType );
//			return node.NodeType == ExpressionType.MemberAccess && ((MemberExpression)node).Member.DeclaringType
			//return node.Find<MemberExpression>( me => this.instanceMap.ContainsKey( me.Member.DeclaringType ) ) != null;
		}


		public virtual Expression Expand( Expression node )
		{
			string propertyName = node.GetPropertyName();

		}

		
	}


	
	public interface IExpressionExpander
	{

		//bool ShouldExpand( Expression expression );

		Expression Expand( Expression source );
	}

	public class DataObjectExpander : ExpressionVisitor, IExpressionExpander
	{

		public Expression Expand( Expression source )
		{
		}
	}

	internal static class TranslatorHelper
	{
	
		public static Type GetType( Expression exp )
		{
			if ( typeof( Queryable ).IsAssignableFrom( exp.Type )
				&& exp.Type.IsGenericType )
				return exp.Type.GetGenericArguments()[ 0 ];

			return exp.Type;
		}
	}
}
